using System;
using System.IO;
using System.Net;
using System.Text;
using AndyPike.Unfuddle.Contracts;
using AndyPike.Unfuddle.Entities.Projects;
using AndyPike.Unfuddle.Exceptions;
using AndyPike.Unfuddle.Services;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Mocks;

namespace AndyPike.Unfuddle.Tests.Unit.Services
{
    [TestFixture]
    public class When_invoking_a_new_web_request
    {
        private MockRepository mockery;
        private IUnfuddleService service;
        private HttpWebRequest request;
        private HttpWebResponse response;

        [SetUp]
        public void SetUp()
        {
            mockery = new MockRepository();
            request = mockery.DynamicMock<HttpWebRequest>();
            response = mockery.DynamicMock<HttpWebResponse>();
            service = new XmlUnfuddleService(false, "subdomain", "username", "password")
                          {
                              HttpWebRequestFactory = mockery.DynamicMock<IHttpWebRequestFactory>()
                          };
        }

        [Test]
        public void Should_return_deserialized_object()
        {
            const string xml = "<project><id>12345</id><title nil=\"true\">This should be removed</title></project>";
            
            using (mockery.Record())
            {
                Expect.Call(service.HttpWebRequestFactory.Create(null, null, null, HttpMethod.GET)).IgnoreArguments().Return(request);
                Expect.Call(request.GetResponse()).Return(response);
                Expect.Call(response.StatusCode).Return(HttpStatusCode.OK);
                Expect.Call(response.GetResponseStream()).Return(new MemoryStream(Encoding.ASCII.GetBytes(xml)));
            }

            Project project;
            using (mockery.Playback())
            {
                project = service.InvokeRequest<Project>("http://www.domain.com");    
            }

            Assert.That(project.Id, Is.EqualTo(12345));
            Assert.That(project.Title, Is.Null);
        }

        [Test]
        public void Should_serialize_an_entity_as_the_request_body()
        {
            var stream = new MemoryStream();

            using (mockery.Record())
            {
                Expect.Call(service.HttpWebRequestFactory.Create("http://www.domain.com", "username", "password", HttpMethod.PUT)).Return(request);
                Expect.Call(request.GetRequestStream()).Return(stream);
                Expect.Call(request.GetResponse()).Return(response);
                Expect.Call(response.StatusCode).Return(HttpStatusCode.OK).Repeat.Any();
            }

            Project result;
            using (mockery.Playback())
            {
                result = service.InvokeRequest("http://www.domain.com", HttpMethod.PUT, new Project {Id = 1});
            }

            Assert.That(result, Is.Null);
        }

        [Test]
        [ExpectedException(typeof(BadRequestException))]
        public void Should_throw_an_exception_if_a_WebException_is_thrown_when_reading_the_response()
        {
            using (mockery.Record())
            {
                Expect.Call(service.HttpWebRequestFactory.Create(null, null, null, HttpMethod.GET)).IgnoreArguments().Return(request);
                Expect.Call(request.GetResponse()).Throw(new WebException("A web exception", null, WebExceptionStatus.ProtocolError, response));
                Expect.Call(response.StatusCode).Return(HttpStatusCode.BadRequest);
            }

            using (mockery.Playback())
            {
                service.InvokeRequest<Project>("http://www.domain.com");
            }
        }

        [Test]
        [ExpectedException(typeof(BadRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_400_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.BadRequest);
        }

        [Test]
        [ExpectedException(typeof(UnauthorizedRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_401_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.Unauthorized);
        }

        [Test]
        [ExpectedException(typeof(ForbiddenRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_403_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.Forbidden);
        }

        [Test]
        [ExpectedException(typeof(NotFoundRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_404_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.NotFound);
        }

        [Test]
        [ExpectedException(typeof(MethodNotAllowedRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_405_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.MethodNotAllowed);
        }

        [Test]
        [ExpectedException(typeof(NotAcceptableRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_406_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.NotAcceptable);
        }

        [Test]
        [ExpectedException(typeof(UnsupportedMediaTypeRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_415_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.UnsupportedMediaType);
        }

        [Test]
        [ExpectedException(typeof(InternalServerErrorRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_a_500_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.InternalServerError);
        }

        [Test]
        [ExpectedException(typeof(UnexpectedRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_an_unexpected_status_code()
        {
            InvalidResponseStatusCode(HttpStatusCode.Redirect);
        }

        [Test]
        [ExpectedException(typeof(UnexpectedRequestException))]
        public void Should_throw_an_exception_if_the_server_returns_an_unexpected_status_code_and_there_are_no_error_messages()
        {
            InvalidResponseStatusCode(HttpStatusCode.Redirect, string.Empty, 0);
        }

        private void InvalidResponseStatusCode(HttpStatusCode statusCode)
        {
            const string xml = "<errors><error>This is an error message returned by the server</error></errors>";
            InvalidResponseStatusCode(statusCode, xml, 1);
        }

        private void InvalidResponseStatusCode(HttpStatusCode statusCode, string errorsXml, int expectedErrors)
        {
            using (mockery.Record())
            {
                Expect.Call(service.HttpWebRequestFactory.Create(null, null, null, HttpMethod.GET)).IgnoreArguments().Return(request);
                Expect.Call(request.GetResponse()).Return(response);
                Expect.Call(response.StatusCode).Return(statusCode).Repeat.Any();
                Expect.Call(response.GetResponseStream()).Return(new MemoryStream(Encoding.ASCII.GetBytes(errorsXml)));
            }
           
            try
            {
                using (mockery.Playback())
                {
                    service.InvokeRequest<Project>("http://www.domain.com");
                }
            }
            catch (RequestException ex)
            {
                Assert.That(ex.Errors.ErrorMessages.Count, Is.EqualTo(expectedErrors));
                throw;
            }
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Should_throw_an_exception_if_the_url_is_null()
        {
            service.InvokeRequest<Project>(null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void Should_throw_an_exception_if_the_url_is_empty()
        {
            service.InvokeRequest<Project>(string.Empty);
        }
    }


    [TestFixture]
    public class When_executing_a_command_with_the_unfuddle_xml_service
    {
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Should_throw_an_exception_if_the_command_is_null()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");
            service.Execute<string>(null);
        }

        [Test]
        public void Should_execute_the_supplied_find_command()
        {
            var mockery = new MockRepository();
            var command = mockery.DynamicMock<IUnfuddleFindCommand<Project>>();
            var expectedProject = new Project();
            Project actualProject;

            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");

            using (mockery.Record())
            {
                Expect.Call(command.Execute(service)).Return(expectedProject);
            }

            using (mockery.Playback())
            {
                actualProject = service.Execute(command);
            }

            Assert.That(actualProject, Is.EqualTo(expectedProject));
        }

        [Test]
        public void Should_execute_the_supplied_change_command()
        {
            var mockery = new MockRepository();
            var command = mockery.DynamicMock<IUnfuddleChangeCommand>();

            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");

            using (mockery.Record())
            {
                Expect.Call(() => command.Execute(service));
            }

            using (mockery.Playback())
            {
                service.Execute(command);
            }
        }
    }

    [TestFixture]
    public class When_building_the_request_url_with_the_unfuddle_xml_service
    {
        [Test]
        public void Should_create_a_valid_https_url()
        {
            IUnfuddleService service = new XmlUnfuddleService(true, "subdomain", "username", "password");
            string url = service.BuildUrl("projects");

            Assert.That(url, Is.EqualTo("https://subdomain.unfuddle.com/api/v1/projects.xml"));
        }

        [Test]
        public void Should_create_a_valid_http_url()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");
            string url = service.BuildUrl("projects");

            Assert.That(url, Is.EqualTo("http://subdomain.unfuddle.com/api/v1/projects.xml"));
        }

        [Test]
        public void Should_create_a_valid_url_with_args()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");
            string url = service.BuildUrl("1/{0}/3/{1}", 2, 4);

            Assert.That(url, Is.EqualTo("http://subdomain.unfuddle.com/api/v1/1/2/3/4.xml"));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Should_throw_an_exception_if_the_action_is_null()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");
            service.BuildUrl(null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void Should_throw_an_exception_if_the_action_is_empty()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");
            service.BuildUrl(string.Empty);
        }
    }

    [TestFixture]
    public class When_instantiating_the_unfuddle_xml_service
    {
        [Test]
        public void Should_not_throw_an_exception_for_valid_data()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", "password");
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Should_throw_an_exception_if_the_subdomain_is_null()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, null, "username", "password");
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Should_throw_an_exception_if_the_username_is_null()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", null, "password");
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Should_throw_an_exception_if_the_password_is_null()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", null);
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void Should_throw_an_exception_if_the_subdomain_is_empty()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, string.Empty, "username", "password");
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void Should_throw_an_exception_if_the_username_is_empty()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", string.Empty, "password");
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void Should_throw_an_exception_if_the_password_is_empty()
        {
            IUnfuddleService service = new XmlUnfuddleService(false, "subdomain", "username", string.Empty);
        }
    }
}